/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.ui.core.widget;

import java.util.List;
import java.util.Vector;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ScrollBar;
import org.pentaho.di.core.gui.TextFileInputFieldInterface;
import org.pentaho.di.ui.core.PropsUI;
import org.pentaho.di.ui.core.gui.GUIResource;

/**
 * Widget to draw the character of a fixed length text-file in a graphical way.
 *
 * @author Matt
 * @since 17-04-2004
 */
public class TableDraw extends Canvas {
  private Display display;
  private Color bg;
  // private Font font;
  private Color black, red, blue, lgray;
  private Point offset;
  private ScrollBar hori;
  private ScrollBar vert;
  private Image dummy_image;
  private GC dummy_gc;
  private int maxlen;

  private int fontheight;
  private int fontwidth;

  private Image cache_image;
  private int prev_fromx, prev_tox, prev_fromy, prev_toy;

  private Vector<TextFileInputFieldInterface> fields;

  private List<String> rows;

  private static final int LEFT = 50;
  private static final int TOP = 30;
  private static final int MARGIN = 10;

  private int potential_click;

  private WizardPage wPage;
  private String prevfieldname;

  public TableDraw( Composite parent, PropsUI props, WizardPage wPage, Vector<TextFileInputFieldInterface> fields ) {
    super( parent, SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL );
    this.wPage = wPage;
    this.fields = fields;

    prevfieldname = "";

    potential_click = -1;

    // Cache displayed text...
    cache_image = null;
    prev_fromx = -1;
    prev_tox = -1;
    prev_fromy = -1;
    prev_toy = -1;

    display = parent.getDisplay();
    bg = GUIResource.getInstance().getColorBackground();
    // font = GUIResource.getInstance().getFontGrid();
    fontheight = props.getGridFont().getHeight();

    black = GUIResource.getInstance().getColorBlack();
    red = GUIResource.getInstance().getColorRed();
    blue = GUIResource.getInstance().getColorBlue();
    lgray = GUIResource.getInstance().getColorLightGray();

    hori = getHorizontalBar();
    vert = getVerticalBar();

    // Determine font width...
    dummy_image = new Image( display, 1, 1 );
    dummy_gc = new GC( dummy_image );
    // dummy_gc.setFont(font);
    String teststring = "ABCDEF";
    fontwidth = Math.round( dummy_gc.textExtent( teststring ).x / teststring.length() );

    setBackground( bg );
    // setFont(font);

    addPaintListener( new PaintListener() {
      public void paintControl( PaintEvent e ) {
        TableDraw.this.paintControl( e );
      }
    } );

    addDisposeListener( new DisposeListener() {
      public void widgetDisposed( DisposeEvent arg0 ) {
        dummy_gc.dispose();
        dummy_image.dispose();

        if ( cache_image != null ) {
          cache_image.dispose();
        }
      }
    } );

    hori.addSelectionListener( new SelectionAdapter() {
      public void widgetSelected( SelectionEvent e ) {
        redraw();
      }
    } );
    vert.addSelectionListener( new SelectionAdapter() {
      public void widgetSelected( SelectionEvent e ) {
        redraw();
      }
    } );
    hori.setThumb( 100 );
    vert.setThumb( 100 );

    // Mouse events!

    addMouseListener( new MouseAdapter() {
      public void mouseDown( MouseEvent e ) {
        Point offset = getOffset();
        int posx = (int) Math.round( (double) ( e.x - LEFT - MARGIN - offset.x ) / ( (double) fontwidth ) );

        if ( posx > 0 ) {
          potential_click = posx;

          redraw();
        }
      }

      public void mouseUp( MouseEvent e ) {
        if ( potential_click > 0 ) {
          setMarker( potential_click ); // clear or set!
          potential_click = -1;

          redraw();
        }
      }
    } );

    addMouseMoveListener( new MouseMoveListener() {
      public void mouseMove( MouseEvent e ) {
        int posx = (int) Math.round( (double) ( e.x - LEFT - MARGIN - offset.x ) / ( (double) fontwidth ) );

        // Clicked and mouse is down: move marker to a new location...
        if ( potential_click >= 0 ) {
          if ( posx > 0 ) {
            potential_click = posx;
            redraw();
          }
        }

        TextFileInputFieldInterface field = getFieldOnPosition( posx );
        if ( field != null && !field.getName().equalsIgnoreCase( prevfieldname ) ) {
          setToolTipText( field.getName() + " : length=" + field.getLength() );
          prevfieldname = field.getName();
        }
      }
    } );
  }

  private TextFileInputFieldInterface getFieldOnPosition( int x ) {
    for ( int i = 0; i < fields.size(); i++ ) {
      TextFileInputFieldInterface field = fields.get( i );
      int pos = field.getPosition();
      int len = field.getLength();
      if ( pos <= x && pos + len > x ) {
        return field;
      }
    }
    return null;

  }

  private void setMarker( int x ) {
    int idx = -1;
    int highest_smaller = -1;

    for ( int i = 0; i < fields.size(); i++ ) {
      TextFileInputFieldInterface field = fields.get( i );

      int pos = field.getPosition();
      int len = field.getLength();

      if ( pos == potential_click ) {
        idx = i;
      }
      if ( highest_smaller < 0 && pos + len >= x ) {
        highest_smaller = i; // The first time this occurs is OK.
      }
    }

    // OK, so we need to add a new field on the location of the previous one
    // Actually, we make the previous field shorter
    //
    // Field 1: pos=0, length=100
    //
    // becomes
    //
    // Field1: pos=0, length=50
    // Field2: pos=51, length=50
    //
    // We know the number of the new field : lowest_larger.
    //
    // Note: We should always have one field @ position 0, length max
    //
    if ( idx < 0 ) { // Position is not yet in the list, the field is not deleted, but added
      if ( highest_smaller >= 0 ) {
        // OK, let's add a new field, but split the length of the previous field
        // We want to keep this list sorted, so add at position lowest_larger.
        // We want to change the previous entry and add another after it.
        TextFileInputFieldInterface prevfield = fields.get( highest_smaller );
        int newlength = prevfield.getLength() - ( x - prevfield.getPosition() );
        TextFileInputFieldInterface field = prevfield.createNewInstance( getNewFieldname(), x, newlength );
        fields.add( highest_smaller + 1, field );

        // Don't forget to make the previous field shorter
        prevfield.setLength( x - prevfield.getPosition() );
      }
    } else {
      if ( highest_smaller >= 0 ) {
        // Now we need to remove the field with the same starting position
        // The previous field need to receive extra length
        TextFileInputFieldInterface prevfield = fields.get( highest_smaller );
        TextFileInputFieldInterface field = fields.get( idx );
        prevfield.setLength( prevfield.getLength() + field.getLength() );
        // Remove the field
        fields.remove( idx );
      }
    }

    // Something has changed: change wizard page...
    wPage.setPageComplete( wPage.canFlipToNextPage() );
  }

  // Loop from 1 to ... and see if we have an empty Fieldnr available.
  private String getNewFieldname() {
    int nr = 1;
    String name = "Field" + nr;
    while ( fieldExists( name ) ) {
      nr++;
      name = "Field" + nr;
    }
    return name;
  }

  private boolean fieldExists( String name ) {
    for ( int i = 0; i < fields.size(); i++ ) {
      TextFileInputFieldInterface field = fields.get( i );

      if ( name.equalsIgnoreCase( field.getName() ) ) {
        return true;
      }
    }
    return false;
  }

  public void setRows( List<String> rows ) {
    this.rows = rows;
    maxlen = getMaxLength();
    redraw();
  }

  // Draw the widget.
  public void paintControl( PaintEvent e ) {
    offset = getOffset();
    if ( offset == null ) {
      return;
    }

    Point area = getArea();
    Point max = getMaximum();
    Point thumb = getThumb( area, max );

    hori.setThumb( thumb.x );
    vert.setThumb( thumb.y );

    // From where do we need to draw?
    int fromy = -offset.y / ( fontheight + 2 );
    int toy = fromy + ( area.y / ( fontheight + 2 ) );
    int fromx = -offset.x / fontwidth;
    int tox = fromx + ( area.x / fontwidth );

    Image image = new Image( display, area.x, area.y );

    if ( fromx != prev_fromx || fromy != prev_fromy || tox != prev_tox || toy != prev_toy ) {
      if ( cache_image != null ) {
        cache_image.dispose();
        cache_image = null;
      }
      cache_image = new Image( display, area.x, area.y );

      GC gc = new GC( cache_image );

      // We have a cached image: draw onto it!
      int linepos = TOP - 5;

      gc.setBackground( bg );
      gc.fillRectangle( LEFT, TOP, area.x, area.y );

      // We draw in black...
      gc.setForeground( black );
      // gc.setFont(font);

      // Draw the text
      //
      for ( int i = fromy; i < rows.size() && i < toy; i++ ) {
        String str = rows.get( i );
        for ( int p = fromx; p < str.length() && p < tox; p++ ) {
          gc.drawText( "" + str.charAt( p ), LEFT + MARGIN + p * fontwidth + offset.x, TOP
            + i * ( fontheight + 2 ) + offset.y, true );
        }

        if ( str.length() < tox ) {
          gc.setForeground( red );
          gc.setBackground( red );
          int x_oval = LEFT + MARGIN + str.length() * fontwidth + offset.x;
          int y_oval = TOP + i * ( fontheight + 2 ) + offset.y;
          gc.drawOval( x_oval, y_oval, fontwidth, fontheight );
          gc.fillOval( x_oval, y_oval, fontwidth, fontheight );
          gc.setForeground( black );
          gc.setBackground( bg );
        }
      }

      // Draw the rulers
      // HORIZONTAL
      gc.setBackground( lgray );
      gc.fillRectangle( LEFT + MARGIN, 0, area.x, linepos + 1 );
      gc.setBackground( bg );

      gc.drawLine( LEFT + MARGIN, linepos, area.x, linepos );

      // Little tabs, small ones, every 5 big one, every 10 the number above...
      for ( int i = fromx; i < maxlen + 10 && i < tox + 10; i++ ) {
        String number = "" + i;
        int numsize = number.length() * fontwidth;

        if ( i > 0 && ( i % 10 ) == 0 ) {
          gc.drawText(
            "" + i, LEFT + MARGIN + i * fontwidth - numsize / 2 + offset.x, linepos - 10 - fontheight, true );
        }

        if ( i > 0 && ( i % 5 ) == 0 ) {
          gc.drawLine(
            LEFT + MARGIN + i * fontwidth + offset.x, linepos, LEFT + MARGIN + i * fontwidth + offset.x,
            linepos - 5 );
        } else {
          gc.drawLine(
            LEFT + MARGIN + i * fontwidth + offset.x, linepos, LEFT + MARGIN + i * fontwidth + offset.x,
            linepos - 3 );
        }
      }

      // VERTICIAL
      gc.setBackground( lgray );
      gc.fillRectangle( 0, TOP, LEFT, area.y );

      gc.drawLine( LEFT, TOP, LEFT, area.y );

      for ( int i = fromy; i < rows.size() && i < toy; i++ ) {
        String number = "" + ( i + 1 );
        int numsize = number.length() * fontwidth;
        gc.drawText( number, LEFT - 5 - numsize, TOP + i * ( fontheight + 2 ) + offset.y, true );
        gc.drawLine( LEFT, TOP + ( i + 1 ) * ( fontheight + 2 ) + offset.y, LEFT - 5, TOP
          + ( i + 1 ) * ( fontheight + 2 ) + offset.y );
      }

      gc.dispose();
    }

    GC gc = new GC( image );

    // Draw the cached image onto the canvas image:
    gc.drawImage( cache_image, 0, 0 );

    // Also draw the markers...
    gc.setForeground( red );
    gc.setBackground( red );
    for ( int i = 0; i < fields.size(); i++ ) {
      int x = ( fields.get( i ) ).getPosition();
      if ( x >= fromx && x <= tox ) {
        drawMarker( gc, x, area.y );
      }
    }
    if ( potential_click >= 0 ) {
      gc.setForeground( blue );
      gc.setBackground( blue );
      drawMarker( gc, potential_click, area.y );
    }

    // Draw the image:
    e.gc.drawImage( image, 0, 0 );
    gc.dispose();
    image.dispose();
  }

  private void drawMarker( GC gc, int x, int maxy ) {
    int[] triangle =
      new int[] {
        LEFT + MARGIN + x * fontwidth + offset.x, TOP - 4, LEFT + MARGIN + x * fontwidth + offset.x + 3,
        TOP + 1, LEFT + MARGIN + x * fontwidth + offset.x - 3, TOP + 1 };
    gc.fillPolygon( triangle );
    gc.drawPolygon( triangle );
    gc
      .drawLine(
        LEFT + MARGIN + x * fontwidth + offset.x, TOP + 1, LEFT + MARGIN + x * fontwidth + offset.x, maxy );
  }

  private Point getOffset() {
    Point area = getArea();
    Point max = getMaximum();
    Point thumb = getThumb( area, max );
    Point offset = getOffset( thumb, area );

    return offset;
  }

  private Point getThumb( Point area, Point max ) {
    Point thumb = new Point( 0, 0 );
    if ( max.x <= area.x ) {
      thumb.x = 100;
    } else {
      thumb.x = Math.round( 100 * area.x / max.x );
    }
    if ( max.y <= area.y ) {
      thumb.y = 100;
    } else {
      thumb.y = Math.round( 100 * area.y / max.y );
    }

    return thumb;
  }

  private Point getOffset( Point thumb, Point area ) {
    Point p = new Point( 0, 0 );
    Point sel = new Point( hori.getSelection(), vert.getSelection() );

    if ( thumb.x == 0 || thumb.y == 0 ) {
      return p;
    }

    p.x = Math.round( -sel.x * area.x / thumb.x );
    p.y = Math.round( -sel.y * area.y / thumb.y );

    return p;
  }

  private Point getMaximum() {
    int maxx = 0;
    int maxy = ( rows.size() + 10 ) * ( fontheight + 2 );

    for ( int i = 0; i < rows.size(); i++ ) {
      String str = rows.get( i );
      int len = ( str.length() + 10 ) * fontwidth;

      if ( maxx < len ) {
        maxx = len;
      }
    }

    return new Point( maxx, maxy );
  }

  private int getMaxLength() {
    int maxx = 0;

    for ( int i = 0; i < rows.size(); i++ ) {
      String str = rows.get( i );
      int len = str.length();

      if ( maxx < len ) {
        maxx = len;
      }
    }

    return maxx;
  }

  private Point getArea() {
    Rectangle rect = getClientArea();
    Point area = new Point( rect.width, rect.height );

    return area;
  }

  public Vector<TextFileInputFieldInterface> getFields() {
    return fields;
  }

  public void setFields( Vector<TextFileInputFieldInterface> fields ) {
    this.fields = fields;
  }

  public void clearFields() {
    fields = new Vector<TextFileInputFieldInterface>();
  }

}
